#pragma once

//#include <streambuf>

template<typename elem_t, typename traits_t>
class basic_dbgstreambuf : public std::basic_streambuf<elem_t, traits_t>
{
public:

    //typedef std::basic_streambuf<elem_t, traits_t> base_t;

    basic_dbgstreambuf();
    ~basic_dbgstreambuf();

protected:

    std::streamsize plen() const;
    std::streamsize ppos() const;
    void ppos(std::streamsize);

	int_type overflow(int_type c);

    std::streamsize xsputn(const elem_t*, std::streamsize);

    int sync();

    pos_type seekoff(
        off_type off,
        std::ios_base::seekdir way,
        std::ios_base::openmode which);

    pos_type seekpos(
        pos_type pos,
        std::ios_base::openmode which);

private:

    basic_dbgstreambuf(const basic_dbgstreambuf<elem_t, traits_t>&);
    
    basic_dbgstreambuf<elem_t, traits_t>& 
        operator=(const basic_dbgstreambuf<elem_t, traits_t>&);

    //void resize(std::basic_string<TCHAR>::size_type);
    //std::basic_string<TCHAR> m_buf;
    elem_t* m_buf;

};


template<typename elem_t, typename traits_t>
inline basic_dbgstreambuf<elem_t, traits_t>::basic_dbgstreambuf()
    : m_buf(0)
{
    //resize(1);
}

template<typename elem_t, typename traits_t>
inline basic_dbgstreambuf<elem_t, traits_t>::~basic_dbgstreambuf()
{
    sync();
    setp(0, 0, 0);
    delete[] m_buf;
}        


template<typename elem_t, typename traits_t>
inline std::streamsize basic_dbgstreambuf<elem_t, traits_t>::plen() const
{
    const ptrdiff_t result = epptr() - pptr();
    return static_cast<std::streamsize>(result);
}


template<typename elem_t, typename traits_t>
inline std::streamsize basic_dbgstreambuf<elem_t, traits_t>::ppos() const
{
    const ptrdiff_t result = pptr() - pbase();
    return static_cast<std::streamsize>(result);
}


template<typename elem_t, typename traits_t>
inline void basic_dbgstreambuf<elem_t, traits_t>::ppos(std::streamsize pos)
{
    pbump(int(pos) - int(ppos()));
}


template<typename elem_t>
void OutputDebugStringX(const elem_t*);

template<>
inline void OutputDebugStringX(const char* str)
{
    ::OutputDebugStringA(str);
}

template<>
inline void OutputDebugStringX(const wchar_t* str)
{
    ::OutputDebugStringW(str);
}


template<typename elem_t, typename traits_t>
inline typename basic_dbgstreambuf<elem_t, traits_t>::int_type 
basic_dbgstreambuf<elem_t, traits_t>::overflow(int_type c_)
{
    if (traits_t::eq_int_type(traits_t::eof(), c_))
        return traits_t::eof();

    const elem_t c = traits_t::to_char_type(c_);

    //sync();
    //NOTE: No, we can't do this here, since dbgview will 
    //break the text across lines when the auto-scroll 
    //option is enabled.
    
    const ptrdiff_t oldlen = epptr() - pbase();
    const ptrdiff_t newlen = oldlen ? 2 * oldlen : 1;
    
    if (elem_t* const newbuf = new (std::nothrow) elem_t[newlen + 1])
    {
        const std::streamsize pos = ppos();        

#if _MSC_VER >= 1400
        const size_t size_in_bytes = newlen * sizeof(elem_t);
        traits_t::_Copy_s(newbuf, size_in_bytes, pbase(), pos);
#else
        traits_t::copy(newbuf, pbase(), pos);        
#endif
        
        setp(newbuf, newbuf + pos, newbuf + newlen);

        delete[] m_buf;
        m_buf = newbuf;    

        *pptr() = c;
        pbump(1);

        return traits_t::not_eof(c_);
    }
    
    if (oldlen)
    {
        sync();
            
        *pbase() = c;
        pbump(1);
    }
    else
    {
        const elem_t str[2] = { c, elem_t() };
        OutputDebugStringX(str);
    }
    
    return traits_t::not_eof(c_);
}


template<typename elem_t, typename traits_t>
inline std::streamsize basic_dbgstreambuf<elem_t, traits_t>::xsputn(
    const elem_t* str, 
    std::streamsize n)
{
    if (n <= plen())
    {
#if _MSC_VER >= 1400
        const size_t size_in_bytes = plen() * sizeof(elem_t);
        traits_t::_Copy_s(pptr(), size_in_bytes, str, n);
#else
        traits_t::copy(pptr(), str, n);
#endif
        
        pbump(n);

        return n;
    }
    
    const std::streamsize pos = ppos();
    const std::streamsize newlen = pos + n;
    
    if (elem_t* const newbuf = new (std::nothrow) elem_t[newlen + 1])
    {
#if _MSC_VER >= 1400
        size_t size_in_bytes = newlen * sizeof(elem_t);
        traits_t::_Copy_s(newbuf, size_in_bytes, pbase(), pos);
#else
        traits_t::copy(newbuf, pbase(), pos);        
#endif

        setp(newbuf, newbuf + pos, newbuf + newlen);

        delete[] m_buf;
        m_buf = newbuf;    

#if _MSC_VER >= 1400
        size_in_bytes = plen() * sizeof(elem_t);
        traits_t::_Copy_s(pptr(), size_in_bytes, str, n);
#else
        traits_t::copy(pptr(), str, n);
#endif
        
        pbump(n);

        return n;
    }
    
    const ptrdiff_t oldlen_ = epptr() - pbase();

    if (oldlen_ == 0)
    {
        elem_t buf[2];
        
        buf[1] = elem_t();
        
        for (std::streamsize i = 0; i < n; ++i)
        {
            buf[0] = *str++;
            OutputDebugStringX(buf);
        }
                    
        return n;
    }
    
    std::streamsize nn = n;
    
    if (std::streamsize len = plen())
    {
#if _MSC_VER >= 1400
        const size_t size_in_bytes = len * sizeof(elem_t);
        traits_t::_Copy_s(pptr(), size_in_bytes, str, len);
#else
        traits_t::copy(pptr(), str, len);
#endif
        
        pbump(len);

        str += len;
        nn -= len;        
    }

    const std::streamsize oldlen = static_cast<std::streamsize>(oldlen_);

#if _MSC_VER >= 1400
    const size_t size_in_bytes = oldlen * sizeof(elem_t);
#endif

    for (;;)
    {    
        sync();
        
        if (nn <= oldlen)
        {
#if _MSC_VER >= 1400
            traits_t::_Copy_s(pbase(), size_in_bytes, str, nn);
#else
            traits_t::copy(pbase(), str, nn);
#endif
            
            pbump(nn);
            
            return n;
        }
        
#if _MSC_VER >= 1400
        traits_t::_Copy_s(pbase(), size_in_bytes, str, oldlen);
#else
        traits_t::copy(pbase(), str, oldlen);
#endif
        
        pbump(oldlen);
        
        str += oldlen;
        nn -= oldlen;
    }
}


template<typename elem_t, typename traits_t>
inline int basic_dbgstreambuf<elem_t, traits_t>::sync()
{
    if (ppos() == 0)  //avoid unnecessary carriage return in dbgview
        return 0;
        
    *pptr() = elem_t();
    OutputDebugStringX(pbase());

    ppos(0);

    return 0;
}
        

//template<typename elem_t, typename traits_t>
//inline void basic_dbgstreambuf<elem_t, traits_t>::resize(std::basic_string<TCHAR>::size_type n)
//{
//    m_buf.resize(n);
//
//    const TCHAR* const const_buf = m_buf.c_str();
//    TCHAR* const buf = const_cast<TCHAR*>(const_buf);
//
//    setp(buf, buf, buf + n);
//}


template<typename elem_t, typename traits_t>
inline typename basic_dbgstreambuf<elem_t, traits_t>::pos_type 
basic_dbgstreambuf<elem_t, traits_t>::seekoff(
    off_type off,
    std::ios_base::seekdir way,
    std::ios_base::openmode which)
{
    off_type pos = -1;

    if (which & std::ios_base::out)
    {
       const ptrdiff_t buflen = epptr() - pbase();
            
        switch (way)
        {
            case std::ios_base::beg:
                pos = off;
                break;

            case std::ios_base::cur:
                pos = off_type(ppos()) + off;
                break;

            case std::ios_base::end:
                pos = off_type(buflen) + off;
                break;
        }

        if (pos < 0)
            pos = -1;
        else if (pos > off_type(buflen))
            pos = -1;
        else
            ppos(pos);
    }

    return pos;
}


template<typename elem_t, typename traits_t>
inline typename basic_dbgstreambuf<elem_t, traits_t>::pos_type 
basic_dbgstreambuf<elem_t, traits_t>::seekpos(
    pos_type pos_,
    std::ios_base::openmode which)
{
    off_type pos = -1;

    if (which & std::ios_base::out)
    {
        pos = pos_;

        const ptrdiff_t buflen = epptr() - pbase();

        if (pos < 0)
            pos = -1;
        else if (pos > off_type(buflen))
            pos = -1;
        else
            ppos(pos);
    }

    return pos;
}
